package com.activeharmony;

/*
 * Copyright (C) 2010 Michael Pardo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.activeharmony.util.IOUtils;
import com.activeharmony.util.Log;
import com.activeharmony.util.NaturalOrderComparator;
import com.activeharmony.util.SQLiteUtils;
import com.activeharmony.util.SqlParser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import ohos.app.Context;
import ohos.global.resource.Entry;

import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;

/**
 * DatabaseHelper class
 */
public final class DatabaseHelper extends SQLiteOpenHelper {
    // ////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC CONSTANTS
    // ////////////////////////////////////////////////////////////////////////////////////

    /**
     * MIGRATION_PATH MIGRATION_PATH
     */
    public static final String MIGRATION_PATH = "migrations";

    // ////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE FIELDS
    // ////////////////////////////////////////////////////////////////////////////////////

    private static final String DEFAULT_DB_PATH_FIRST = "/data/user/0/";

    private static final String DEFAULT_DB_PATH_SECOND = "/databases/";

    private final String mSqlParser;

    // ////////////////////////////////////////////////////////////////////////////////////
    // CONSTRUCTORS
    // ////////////////////////////////////////////////////////////////////////////////////

    /**
     * DatabaseHelper constructor
     *
     * @param configuration configuration
     */
    public DatabaseHelper(Configuration configuration) {
        super(configuration.getContext(),
            DEFAULT_DB_PATH_FIRST + configuration.getPackageName() + DEFAULT_DB_PATH_SECOND
                + configuration.getDatabaseName(), null, configuration.getDatabaseVersion());
        copyAttachedDatabase(configuration.getContext(), configuration.getDatabaseName());
        mSqlParser = configuration.getSqlParser();
    }

    // ////////////////////////////////////////////////////////////////////////////////////
    // OVERRIDEN METHODS
    // ////////////////////////////////////////////////////////////////////////////////////

    @Override
    public void onOpen(SQLiteDatabase db) {
        executePragmas(db);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        executePragmas(db);
        executeCreate(db);
        executeMigrations(db, -1, db.getVersion());
        executeCreateIndex(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        executePragmas(db);
        executeCreate(db);
        executeMigrations(db, oldVersion, newVersion);
    }

    // ////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC METHODS
    // ////////////////////////////////////////////////////////////////////////////////////

    /**
     * copyAttachedDatabase
     *
     * @param context context
     * @param databaseName databaseName
     */
    public void copyAttachedDatabase(Context context, String databaseName) {
        final File dbPath = context.getDatabaseDir();

        // If the database already exists, return
        if (dbPath.exists()) {
            return;
        }

        // Make sure we have a path to the file
        if (dbPath.getParentFile().mkdirs()) {
            Log.i("copyAttachedDatabase mkdirs successful");
        } else {
            Log.i("copyAttachedDatabase mkdirs unsuccessful");
        }

        // Try to copy database file
        try {
            final InputStream inputStream = context.getResourceManager().getRawFileEntry(databaseName).openRawFile();
            final OutputStream output = new FileOutputStream(dbPath);

            byte[] buffer = new byte[8192];
            int length;

            while ((length = inputStream.read(buffer, 0, 8192)) > 0) {
                output.write(buffer, 0, length);
            }

            output.flush();
            output.close();
            inputStream.close();
        } catch (IOException e) {
            Log.e("Failed to open file", e);
        }
    }

    // ////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE METHODS
    // ////////////////////////////////////////////////////////////////////////////////////

    private void executePragmas(SQLiteDatabase db) {
        if (SQLiteUtils.FOREIGN_KEYS_SUPPORTED) {
            db.execSQL("PRAGMA foreign_keys=ON;");
            Log.i("Foreign Keys supported. Enabling foreign key features.");
        }
    }

    private void executeCreateIndex(SQLiteDatabase db) {
        db.beginTransaction();
        try {
            for (TableInfo tableInfo : Cache.getTableInfos()) {
                String[] definitions = SQLiteUtils.createIndexDefinition(tableInfo);

                for (String definition : definitions) {
                    db.execSQL(definition);
                }
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

    private void executeCreate(SQLiteDatabase db) {
        db.beginTransaction();
        try {
            for (TableInfo tableInfo : Cache.getTableInfos()) {
                db.execSQL(SQLiteUtils.createTableDefinition(tableInfo));
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

    private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) {
        boolean migrationExecuted = false;
        try {
            final List<Entry> entries =
                Arrays.asList(Cache.getContext().getResourceManager().getRawFileEntry(MIGRATION_PATH).getEntries());
            Collections.sort(entries, new NaturalOrderComparator());

            db.beginTransaction();
            try {
                for (Entry entry : entries) {
                    try {
                        final int version = Integer.parseInt(entry.getPath().replace(".sql", ""));

                        if (version > oldVersion && version <= newVersion) {
                            executeSqlScript(db, entry.getPath());
                            migrationExecuted = true;

                            Log.i(entry + " executed succesfully.");
                        }
                    } catch (NumberFormatException e) {
                        Log.w("Skipping invalidly named file: " + entry, e);
                    }
                }
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        } catch (IOException e) {
            Log.e("Failed to execute migrations.", e);
        }

        return migrationExecuted;
    }

    private void executeSqlScript(SQLiteDatabase db, String file) {
        InputStream stream = null;

        try {
            stream = Cache.getContext().getResourceManager().getRawFileEntry(MIGRATION_PATH + "/" + file).openRawFile();
            if (Configuration.SQL_PARSER_DELIMITED.equalsIgnoreCase(mSqlParser)) {
                executeDelimitedSqlScript(db, stream);

            } else {
                executeLegacySqlScript(db, stream);
            }

        } catch (IOException e) {
            Log.e("Failed to execute " + file, e);

        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    private void executeDelimitedSqlScript(SQLiteDatabase db, InputStream stream) throws IOException {
        List<String> commands = SqlParser.parse(stream);

        for (String command : commands) {
            db.execSQL(command);
        }
    }

    private void executeLegacySqlScript(SQLiteDatabase db, InputStream stream) throws IOException {
        InputStreamReader reader = null;
        BufferedReader buffer = null;

        try {
            reader = new InputStreamReader(stream);
            buffer = new BufferedReader(reader);
            String line = null;

            while ((line = buffer.readLine()) != null) {
                line = line.replace(";", "").trim();
                if (!line.isEmpty()) {
                    db.execSQL(line);
                }
            }

        } finally {
            IOUtils.closeQuietly(buffer);
            IOUtils.closeQuietly(reader);
        }
    }
}
